#include "stdafx.h"
#include "BasicSharedTimer.h"
#include <mmsystem.h>
#include "BasicThreadTimers.h"

#pragma comment(lib,"Winmm.lib")

//extern HMODULE gThisDll;

namespace BasicSkinTimer
{
	static UINT gTimerFiredMessage = 0;
	enum
	{
		sharedTimerID = 1000,
		endHighResTimerID = 1001,
	};

	BasicOffScreenTimerWindow::BasicOffScreenTimerWindow()
		: m_hWnd(NULL)
		, m_pSharedTimer(NULL)
		, m_bhighResTimerActive(false)
		, m_bProcessingCustomTimerMessage(false)
		, m_nPendingTimers(0)
		, m_hTimer(NULL)
		, m_hTimerQueue(NULL)
		, m_ntimerID(0)
	{
	}

	BasicOffScreenTimerWindow::~BasicOffScreenTimerWindow()
	{
		if (m_hTimerQueue && m_hTimer)
		{
			DeleteTimerQueueTimer(m_hTimerQueue, m_hTimer, 0);
		}
		if (m_hTimerQueue)
			DeleteTimerQueue(m_hTimerQueue);
	}
	
	LRESULT CALLBACK TimerWindowWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		if (message == WM_NCCREATE)
		{
			LPCREATESTRUCT pStr = (LPCREATESTRUCT) lParam;
			::SetWindowLongPtr(hWnd,GWLP_USERDATA,(LONG_PTR)pStr->lpCreateParams);
		}
		BasicOffScreenTimerWindow* pWindow = (BasicOffScreenTimerWindow*)GetWindowLongPtr(hWnd,GWLP_USERDATA);
		if (pWindow)
			return pWindow->OnTimerWindowProc(hWnd,message,wParam,lParam);
		return DefWindowProc(hWnd, message, wParam, lParam);
	}

	bool BasicOffScreenTimerWindow::CreateTimerWindow()
	{
		if (m_hWnd)
			return true;

		static bool bRegTimerWindow = false;

		HMODULE hModule = GetModuleHandle(NULL);

		if (!bRegTimerWindow)
		{
			WNDCLASSEX wcex;
			memset(&wcex, 0, sizeof(WNDCLASSEX));
			wcex.cbSize = sizeof(WNDCLASSEX);
			wcex.lpfnWndProc    = TimerWindowWndProc;
			wcex.hInstance      = hModule/*gThisDll*/;
			wcex.lpszClassName  = L"BasicTimerWindow";
			bRegTimerWindow = !!RegisterClassExW(&wcex);

			gTimerFiredMessage = RegisterWindowMessage(L"BasicSkin.TimerFired");

		}

		m_hWnd = CreateWindow(L"BasicTimerWindow", 0, 0,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, HWND_MESSAGE, 0, hModule/*gThisDll*/, this);
		return m_hWnd != NULL;
	}

	LRESULT BasicOffScreenTimerWindow::OnTimerWindowProc( HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam )
	{
		if (uMsg == WM_DESTROY)
		{
			::SetWindowLongPtr(hWnd,GWLP_USERDATA,0);
			m_hWnd = NULL;
		}
		if (uMsg == WM_CHECKNESTEDLOOP)
		{
			// Is In Nested Loop
			if (m_pSharedTimer 
				&& m_pSharedTimer->m_pThreadTimers 
				&& m_pSharedTimer->m_pThreadTimers->m_nCurrentLoop == wParam)
			{
				m_pSharedTimer->m_pThreadTimers->fireTimersInNestedEventLoop();
			}
			return true;
		}
		if (uMsg == WM_TIMER)
		{
			if (wParam == sharedTimerID)
			{
				//DEBUG_FUN_TIME();
				::KillTimer(hWnd,sharedTimerID);
				if (m_pSharedTimer)
					m_pSharedTimer->m_pSharedTimerFiredFunction();

				return 0;
			}
			else if(wParam == endHighResTimerID)
			{
				KillTimer(hWnd, endHighResTimerID);
				m_bhighResTimerActive = false;
				timeEndPeriod(1);
			}
		}
		else if (uMsg == gTimerFiredMessage)
		{
			::InterlockedExchange(&m_nPendingTimers,0);

			m_bProcessingCustomTimerMessage = true;
			if (m_pSharedTimer)
				m_pSharedTimer->m_pSharedTimerFiredFunction();
			m_bProcessingCustomTimerMessage = false;
			return 0;
		}


		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	bool BasicOffScreenTimerWindow::shouldUseHighResolutionTimers()
	{
		return true;
	}

	static void NTAPI queueTimerProc(PVOID lpParameter, BOOLEAN)
	{
		BasicOffScreenTimerWindow* pWnd = (BasicOffScreenTimerWindow*)lpParameter; 
		if (InterlockedIncrement(&pWnd->m_nPendingTimers) == 1)
			PostMessage(pWnd->m_hWnd, gTimerFiredMessage, 0, 0);
	}

	void BasicOffScreenTimerWindow::SetFireInterval( double interval )
	{
		unsigned intervalInMS;
		//interval = 1000;

		if (interval > USER_TIMER_MAXIMUM)
			intervalInMS = USER_TIMER_MAXIMUM;
		else
			intervalInMS = static_cast<unsigned>(interval);

		if (m_hWnd == NULL)
			CreateTimerWindow();

		bool timerSet = false;

		if (shouldUseHighResolutionTimers() && interval < 16)
		{
			if (interval < 16)
			{
				if (!m_bhighResTimerActive)
				{
					m_bhighResTimerActive = true;
					timeBeginPeriod(1);
				}
				::SetTimer(m_hWnd, endHighResTimerID, 300, 0);
			}

			DWORD queueStatus = LOWORD(GetQueueStatus(QS_PAINT | QS_MOUSEBUTTON | QS_KEY | QS_RAWINPUT));

			if (!(queueStatus & (QS_MOUSEBUTTON | QS_KEY | QS_RAWINPUT))) 
			{
				if (intervalInMS < USER_TIMER_MINIMUM && !m_bProcessingCustomTimerMessage && !(queueStatus & QS_PAINT))
				{
					if (InterlockedIncrement(&m_nPendingTimers) == 1)
						PostMessage(m_hWnd, gTimerFiredMessage, 0, 0);
					timerSet = true;
				} 
				else
				{
					if (!m_hTimerQueue)
						m_hTimerQueue = CreateTimerQueue();
					if (m_hTimer)
						DeleteTimerQueueTimer(m_hTimerQueue, m_hTimer, 0);
					timerSet = CreateTimerQueueTimer(&m_hTimer, m_hTimerQueue, queueTimerProc, this, intervalInMS, 0, WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
				}
			}
		}

		if (timerSet)
		{
			if (m_ntimerID)
			{
				KillTimer(m_hWnd, m_ntimerID);
				m_ntimerID = 0;
			}
		}
		else
		{
			m_ntimerID = SetTimer(m_hWnd, sharedTimerID, intervalInMS, 0);
			m_hTimer = 0;
		}
	}
	///////////////////////////////////////////////////////////////////////////////


	BasicSharedTimer::BasicSharedTimer()
	{
		m_pThreadTimers = NULL;
		m_timerWnd.m_pSharedTimer = this;
	}

	BasicSharedTimer::~BasicSharedTimer()
	{
		m_pThreadTimers = NULL;
		Stop();
		m_timerWnd.m_pSharedTimer = NULL;
	}

	void BasicSharedTimer::SetFiredFunction( void (*f)() )
	{
		m_pSharedTimerFiredFunction = f;
	}

	void BasicSharedTimer::SetFireInterval( double interval )
	{
		m_timerWnd.SetFireInterval(interval);
	}

	void BasicSharedTimer::Stop()
	{
		if (m_timerWnd.m_hTimerQueue && m_timerWnd.m_hTimer)
		{
			DeleteTimerQueueTimer(m_timerWnd.m_hTimerQueue, m_timerWnd.m_hTimer, 0);
			m_timerWnd.m_hTimer = 0;
		}
		if (m_timerWnd.m_hWnd && m_timerWnd.m_ntimerID) 
		{
			KillTimer(m_timerWnd.m_hWnd, m_timerWnd.m_ntimerID);
			m_timerWnd.m_ntimerID = 0;
		}
	}
	
}